<?php
/**
 * PHP 控制台Trace记录生成器
 * 需要 Gearmand 以及 记录采集服务 watcher (Nodejs) 
 * 
 * 使用方法
 * // log 数据
 * Console::log( $data );
 * 
 * // 警告信息
 * Console::warning( "warning_message" , $somedata=[] );
 * 
 * // 错误信息
 * Console::error( "error_message", $somedata=[] );
 * 
 * // 异常信息
 * Console::exception( new Exception("error message") );
 */

# 自定义 Log 级别属性
define("CONSOLE_CUSTOM_NONE", 0);
define("CONSOLE_CUSTOM_LOG", 1);
define("CONSOLE_CUSTOM_WARNING", 2);
define("CONSOLE_CUSTOM_ERROR", 4);
define("CONSOLE_CUSTOM_ALL", 7);


// 服务配置
# 采集器
if(!defined("CONSOLE_TRACE_GATHER")){
	define("CONSOLE_TRACE_GATHER", "http://console.diandao.org:8999/gather");
}

# 是否监控页面线程
if(!defined("CONSOLE_TRACE_THREAD")){
	define("CONSOLE_TRACE_THREAD", 1);
}

# 监控的错误类型  e.g.  E_ALL ^ E_NOTICE
if(!defined("CONSOLE_TRACE_ERROR")){
	define("CONSOLE_TRACE_ERROR", E_ALL );
}

# 自定义监控信息 ::log()  ::warning()  ::error()
# 关闭所有 Console::$TRACE_LOG = CONSOLE_CUSTOM_NONE;
if(!defined("CONSOLE_TRACE_LOG")){
	define("CONSOLE_TRACE_LOG", CONSOLE_CUSTOM_ALL );
}

# Gearman Host
if(!defined("GEARMAN_HOST")){
	define("GEARMAN_HOST", "127.0.0.1");
}
# Gearman Port
if(!defined("GEARMAN_PORT")){
	define("GEARMAN_PORT", 4730 );
}


/**
 * Set default value to which if variable is unset.
 * 设置默认值，如果变量未赋值
 * @param mixed var - The variable being evaluted.
 * @param mixed default - The variable instead of unset var.
 * @return mixed - &var if var is seted, default otherwish.
 */
function &console_set_def(&$var, $default = null) {
	if (! isset( $var )) $var = $default;
	return $var;
}

/**
 * Console 静态类
 */
class Console {
	const C_LOG = 1;
	const C_WARNING = 2;
	const C_ERROR = 4;
	const C_ALL = 7;

	static $helper;
	static $SERVER;

	static $TRACE_LOG = CONSOLE_TRACE_LOG;
	static $TRACE_ERROR = CONSOLE_TRACE_ERROR;

	public static function _init(){
		self::$SERVER = isset($_SERVER)?$_SERVER:array();
		self::$helper = new ConsoleHelper;
	}

	// log
	public static function log($var){
		return self::$helper->log($var);
	}
	// warning
	public static function warning($msg, $data = array()){
		return self::$helper->warning($msg, $data);
	}
	// error
	public static function error($msg, $data=array()){
		return self::$helper->error($msg, $data);
	}

	// exception
	public static function exception($exception){
		return self::$helper->exception($exception);
	}

	public static function trace($type, $data){
		return self::$helper->trace($type, $data);
	}

	// 获取线程
	public static function thread(){
		return self::$helper->thread();
	}
}


/**
 * ConsoleHelper
 */
class ConsoleHelper {

	private $_thread;	// 线程信息

	private $_gearman_client;

	private $_client_ip;

	public $requestTime;	// 请求时间
	public $destructTime;	// 页面结束时间

	public function __construct(){
		// ip
		$this->_client_ip = $this->clientIP();
		// 线程参数
		$this->_thread = $this->createThreadID();
		$super_thread =  console_set_def($_REQUEST['console_thread'], null);
		if($super_thread){
			$this->_thread = $super_thread."_".$this->_thread;
		}

		// 
		$this->requestTime =  console_set_def(Console::$SERVER['REQUEST_TIME_FLOAT'], microtime(true) );

		$gearmand_disabled = false;
		$this->_gearman_client = new GearmanClient();
		@$fp = fsockopen(GEARMAN_HOST, GEARMAN_PORT, $errno, $errstr, 0.01);  
        if($fp){        
            $fp && fclose($fp);
            $this->_gearman_client->addServer( GEARMAN_HOST, GEARMAN_PORT );
            $gearmand_disabled = false;
        } else {
            $gearmand_disabled = true;
        }
		if($gearmand_disabled) return false;

		// 致命错误
		register_shutdown_function("console_fatal_handler");
		// 捕获错误
		set_error_handler('console_error_handler');
		// 自动捕获异常
		set_exception_handler('console_exception_handler');

		// 线程开始
		$this->_traceThreadStart();
	}

	/**
	 * 线程销毁前回调
	 */
	public function __destruct(){
		$this->_traceThreadEnd();
	}



	/**
	 * log 数据
	 */
	public function log(){
		if( !(Console::$TRACE_LOG & CONSOLE_CUSTOM_LOG) ) return;
		return $this->trace( "log", $this->_pitchBacktrace(debug_backtrace()) );
	}

	/**
	 * 自定义警告
	 */
	public function warning(){
		if( !(Console::$TRACE_LOG & CONSOLE_CUSTOM_WARNING) ) return;
		return $this->trace( "cwarning", $this->_pitchTrace( debug_backtrace()) );
	}

	/**
	 * 自定义错误
	 */
	public function error(){
		if( !(Console::$TRACE_LOG & CONSOLE_CUSTOM_ERROR) ) return;
		return $this->trace( "cerror", $this->_pitchTrace( debug_backtrace()) );
	}

	/**
	 * 定位代码行
	 */ 
	private function _pitchBacktrace($backtrace){
		$point = array_splice($backtrace, 0, 1)[0];
		foreach($backtrace as $t){
			if($t["class"]=="Console"){
				$point = $t;
				break;
			}
		}
		$data = array(
			"file" => $point["file"],
			"line" => $point["line"],
			"args" => $point["args"]
		);
		return $data;
	}

	/**
	 * trace 错误
	 * @param Exception $e, 错误对象 
	 */
	public function exception($e){
		// class Exception ...
		// final function getMessage(); // 返回异常信息
		// final function getCode(); // 返回异常代码
		// final function getFile(); // 返回发生异常的文件名
		// final function getLine(); // 返回发生异常的代码行号
		// final function getTrace(); // backtrace() 数组
		// final function getTraceAsString(); // 已格成化成字符串的 getTrace() 信息 
		return $this->trace("error", array(
			"message"=> $e->getMessage(),
			"code" => $e->getCode(),
			"file" => $e->getFile(),
			"line" => $e->getLine(),
			"traceback" => json_encode($e->getTrace())
		));
	}

	/**
	 * 获取客户端IP
	 */
	public function clientIP(){
	    $unk = "unknown";

	    // 已声明的Client IP
    	$cip = console_set_def(Console::$SERVER["HTTP_CLIENT_IP"],null);
    	if( $cip && strcasecmp($cip, $unk)){ return $cip; }
	    
	    // 代理服务器转发
    	$hxff = console_set_def(Console::$SERVER["HTTP_X_FORWARDED_FOR"],null);
    	if( $hxff && strcasecmp($hxff, $unk)){ return $hxff; }

	    // 代理,  Nginx配置中传递这个参数
    	$xreal = console_set_def(Console::$SERVER["HTTP_X_REAL_IP"],null);
    	if( $xreal && strcasecmp($xreal, $unk)){ return $xreal; }
	   
	    // 直连远程
    	$rmadd = console_set_def(Console::$SERVER['HTTP_REMOTE_ADDR'], null);
    	if( $rmadd && strcasecmp($rmadd, $unk)){ return $rmadd; }

	    // 直连远程
    	$rmadd = console_set_def(Console::$SERVER['REMOTE_ADDR'],null);
    	if( $rmadd && strcasecmp($rmadd, $unk)){ return $rmadd; }
	    return null;
	}


	/**
	 * trace 接口
	 * 需要 CONSOLE_TRACE_ADDR 
	 */
	public function trace($type, $data){
		// 数据拼装
		$post = array(
			"type" => $type,
			"thread" => $this->thread(),
			"timestamp" => time(),
			"fire" => json_encode($data)
		);
		if(!CONSOLE_TRACE_THREAD){
			$post = array_merge($post, $this->_threadData() );
		}
		$this->_asyncRequest($post);
	}

	// 线程开始
	private function _traceThreadStart(){
		if(! CONSOLE_TRACE_THREAD) return;
		$post = array_merge( array("type"=>"threadStart" ), $this->_threadData() );
		$this->_asyncRequest($post);
	}

	/**
	 * 通用页面参数
	 * 
	 */
	private function _threadData(){
		return array(
			"thread" => $this->thread(),
			"timestamp" => time(),
			"host" => console_set_def(Console::$SERVER["HTTP_HOST"],"unknown"),
			"userAgent" => console_set_def(Console::$SERVER["HTTP_USER_AGENT"],"none"),
			"clientIP" => $this->_client_ip,
			"httpMethod" => console_set_def(Console::$SERVER["REQUEST_METHOD"],"unknown"),
			"requestURI" => console_set_def(Console::$SERVER["REQUEST_URI"],"unknown"),
			"cookie" => console_set_def(Console::$SERVER["HTTP_COOKIE"],"")
		);
	}

	// 线程结束
	private function _traceThreadEnd(){
		if(! CONSOLE_TRACE_THREAD) return;
		$this->_asyncRequest(array(
			"type"=>"threadEnd",
			"thread" => $this->thread(),
			"timestamp" => time(),
			"duration" => microtime(true) - $this->requestTime
		));
	}

	/**
	 * 发起异步请求
	 */
	private function _asyncRequest($post){
		// gearmand 无效
		if(!$this->_gearman_client) return false;
		$curl = array();
		$curl[CURLOPT_URL] = CONSOLE_TRACE_GATHER;
		$curl[CURLOPT_POST] = 1;
		$curl[CURLOPT_POSTFIELDS] = $post;
		// 使用gearman异步发起请求
		// 异步进行，只返回处理句柄。
		$this->_gearman_client->doBackground('curl_request', serialize($curl) );
	}

	// 线程
	public function thread(){
		return $this->_thread;
	}

	// 生成唯一的线程ID
	private function createThreadID($namespace = '') {
        static $guid = "";
        $uid = uniqid("", true);
        $data = $namespace;
        $data .= console_set_def(Console::$SERVER['REQUEST_TIME_FLOAT'] ,microtime(true));
        $data .= console_set_def(Console::$SERVER['HTTP_USER_AGENT'],"none");

        $data .= console_set_def(Console::$SERVER['SERVER_ADDR'],"none");
        $data .= console_set_def(Console::$SERVER['SERVER_PORT'],"none");
        $data .= $this->_client_ip;
        $data .= console_set_def(Console::$SERVER['REMOTE_PORT'],"none");

        $guid = strtoupper(hash('ripemd128', $uid. md5($data)));
        // $this::$guid =   
        //         substr($hash,  0,  8).'-'.
        //         substr($hash,  8,  4).'-'.
        //         substr($hash, 12,  4).'-'.
        //         substr($hash, 16,  4).'-'.
        //         substr($hash, 20, 12);
        return $guid;
    }
}

// 初始化
Console::_init();


/**
 * 致命错误
 */
function console_fatal_handler(){
	$error = error_get_last();
	console_error_handler($error['type'], $error['message'], $error['file'], $error['line']);
}

/**
 * 错误捕获
 */
function console_error_handler($error_type, $message, $file, $line, $info=null){

	// 不符合错误配置
	if(!(CONSOLE_TRACE_ERROR & $error_type)) return;

	//	$error_types = array(
	//		1=>'ERROR', 2=>'WARNING', 4=>'PARSE', 8=>'NOTICE', 16=>'CORE_ERROR', 
	//		32=>'CORE_WARNING', 64=>'COMPILE_ERROR', 128=>'COMPILE_WARNING', 256=>'USER_ERROR', 512=>'USER_WARNING', 1024=>'USER_NOTICE', 2047=>'ALL', 2048=>'STRICT'
	//	);
	$data = array("type"=>$error_type,  "message" => $message, "file" => $file, "line" => $line );
	switch($error_type){
		case E_ERROR:
		case E_CORE_ERROR:
		case E_COMPILE_ERROR:
		case E_USER_ERROR:
			Console::trace("error", $data );
			break;
		case E_WARNING:
		case E_CORE_WARNING:
		case E_COMPILE_WARNING:
		case E_USER_WARNING:
			Console::trace("warning", $data );
			break;
		case E_PARSE:
			Console::trace("parse", $data );
			break;
		case E_NOTICE:
		case E_USER_NOTICE:
			console::trace("notice", $data);
			
	}
}

/**
 * 异常捕获
 */
function console_exception_handler($exception){
	Console::exception( $exception );
}



?>